﻿using System;
using System.Collections.Generic;
//using System.Linq;
using System.Text;
using System.Net;
using System.Text.RegularExpressions;

namespace repliback4.Net.Protocol.SASLMechanisms {

    //STEP ONE CHALANAGE
    //
    //  realm | nonce | qop-options | stale | maxbuf | charset | algorithm | cipher-opts | auth-param
    //
    //realm             = "realm" "=" <"> realm-value <">
    //realm-value       = qdstr-val
    //nonce             = "nonce" "=" <"> nonce-value <">
    //nonce-value       = qdstr-val
    //qop-options       = "qop" "=" <"> qop-list <">
    //qop-list          = 1#qop-value
    //qop-value         = "auth" | "auth-int" | "auth-conf" |
    //                     token
    //stale             = "stale" "=" "true"
    //maxbuf            = "maxbuf" "=" maxbuf-value
    //maxbuf-value      = 1*DIGIT
    //charset           = "charset" "=" "utf-8"
    //algorithm         = "algorithm" "=" "md5-sess"
    //cipher-opts       = "cipher" "=" <"> 1#cipher-value <">
    //cipher-value      = "3des" | "des" | "rc4-40" | "rc4" |
    //                    "rc4-56" | token
    //auth-param        = token "=" ( token | quoted-string )



    public class Challange {
        protected List<DigestField> fields = new List<DigestField>( );
        protected List<DigestFieldValidator> validators;// = new List<DigestFieldValidator>( );
        public Challange ( IEnumerable<DigestFieldValidator> validatorsList ) {
            this.validators = new List<DigestFieldValidator>( validatorsList );
        }

        public static Challange StepOne {
            get {
                return new Challange( new DigestFieldValidator[]{
                    new DigestFieldValidator(){Key="realm", Quoted=true,  RequiredCount = RequiredCount.ZeroOrMore},
                    new DigestFieldValidator(){Key="nonce", Quoted=true },
                    new DigestFieldValidator(){Key="qop-options", Quoted=true, DefaultValue="auth"},
                    new DigestFieldValidator(){Key="stale", Quoted=true,  DefaultValue="true"},
                    new DigestFieldValidator(){Key="maxbuf", Quoted=true, RequiredCount = RequiredCount.One },
                    new DigestFieldValidator(){Key="charset", Quoted=false, DefaultValue = "utf-8"},
                    new DigestFieldValidator(){Key="algorithm", Quoted=true,  DefaultValue = "md5-sess"},
                    new DigestFieldValidator(){Key="cipher-opts", Quoted=true },
                    new DigestFieldValidator(){Key="auth-param", Quoted=true}
                } );

            }
        }
    }

    /// <summary>
    /// http://www.faqs.org/rfcs/rfc2831.html
    /// </summary>
    public class Digest {

    }

    public class DigestMD5 : Digest {
        public DigestMD5 ( ) {

        }
    }
    public class Field<TKey, TValue> {
        TKey key;
        TValue value;
        public TKey Key { get { return this.key; } set { this.key = value; } }
        public TValue Value { get { return this.value; } set { this.value = value; } }
    }
    public class DigestField : Field<String, String> {

    }
    public enum RequiredCount { One, OneOrMore, ZeroOrMore }
    public class DigestFieldValidator {
        String key;
        Boolean quoted;
        Regex regex = new Regex("^.*$");
        String defaultValue;
        Predicate<int> countValidator;
        public Predicate<int> CountValidator {
            set { this.countValidator = value; }
            get {
                if ( this.countValidator == null ) { this.RequiredCount = RequiredCount.One; }
                return this.countValidator;
            }
        }

        public Regex Expression {
            get { return this.regex; }
            set { this.regex = value; }
        }
        public String Key {
            set { this.key = value; }
            get { return this.key; }
        }
        public String DefaultValue {
            set { this.defaultValue = value; }
            get { return this.defaultValue; }
        }

        public Boolean Quoted {
            set { this.quoted = value; }
            get { return this.quoted; }
        }

        public RequiredCount RequiredCount {
            //get { return !this.validCount( 0 ); }
            set {
                switch ( value ) {
                    case RequiredCount.One: this.CountValidator = new Predicate<int>( c => c == 1 ); break;
                    case RequiredCount.OneOrMore: this.CountValidator = new Predicate<int>( c => c >= 1 ); break;
                    case RequiredCount.ZeroOrMore: this.CountValidator = new Predicate<int>( c => c >= 0 ); break;
                }
            }
        }

        //public Boolean IsValid ( DigestField field ) {
        //    return
        //        ( this.Required && ( field.Value != null || this.DefaultValue != null ) )
        //        ||
        //        ( !this.Required && ( field.Value != null ) );
        //}
        public string GetAsString ( DigestField field ) {


            //if ( !this.IsValid( field ) ) {
            //    throw new Exception( String.Format( "field [{0}] is required and have no value nor default value", field.Key ) );
            //}
            if ( this.Quoted ) {
                return String.Format( "{0}=\"{1}\"", field.Key, field.Value );
            } else {
                return String.Format( "{0}={1}", field.Key, field.Value );
            }
        }
    }

    public class ValidationException : Exception {
        IEnumerable<String> errors;
        public IEnumerable<String> Errors {
            get { return this.errors; }
        }
        public ValidationException ( String message, IEnumerable<String> errors )
            : base( message ) {
            this.errors = errors;
        }
        public ValidationException ( IEnumerable<String> errors ) {
            this.errors = errors;
        }
        public override string Message {
            get {
                StringBuilder sb = new StringBuilder( );
                if ( base.Message.Length > 0 ) {
                    sb.AppendFormat( "message: {0}\n", base.Message );
                }

                foreach ( String s in this.errors ) {
                    sb.AppendFormat( "\t{0}", s );
                }
                return sb.ToString( );
            }
        }
    }

    public static class FieldExtension {
        public static List<DigestField> GetFields ( this List<DigestField> list, Predicate<DigestField> match ) {
            return list.FindAll( match );
        }

        public static String Join ( this List<DigestField> list, String glue, List<DigestFieldValidator> validators ) {


            validators.Validate( list );
            List<DigestField>.Enumerator enu = list.GetEnumerator( );
            if ( enu.MoveNext( ) ) {
                StringBuilder ret = new StringBuilder( );
                ret.Append( enu.Current.ToString( ) );
                while ( enu.MoveNext( ) ) {
                    ret.AppendFormat( ",{0}", validators.GetValidatorForField( enu.Current.Key ).GetAsString( enu.Current ) );
                }
                return ret.ToString( );
            } else {
                return "";
            }

        }

        public static List<DigestField> GetFieldsByKey ( this List<DigestField> list, String key ) {
            return list.FindAll( new Predicate<DigestField>(
                ( DigestField f ) => f.Key == key
            ) );
        }

        public static DigestField GetFirstFieldByKey ( this List<DigestField> list, String key ) {
            return list.Find( new Predicate<DigestField>(
                ( DigestField f ) => f.Key == key
            ) );
        }
        public static DigestField GetLastFieldByKey ( this List<DigestField> list, String key ) {
            return list.FindLast( new Predicate<DigestField>(
                ( DigestField f ) => f.Key == key
            ) );
        }

        public static DigestFieldValidator GetValidatorForField ( this List<DigestFieldValidator> validators, String fieldName ) {
            return validators.Find( new Predicate<DigestFieldValidator>( ( DigestFieldValidator v ) => v.Key == fieldName ) );
        }

        //public static String[] GetKeysArray ( this List<DigestField> list ) {
        //    HashSet<String> set = list.GetKeysSet( );

        //    String[] ret = new String[set.Count];
        //    set.CopyTo( ret );
        //    return ret;
        //}
        public static HashSet<String> GetKeysSet ( this List<DigestField> list ) {

            HashSet<String> set = new HashSet<string>( );
            foreach ( DigestField f in list ) {
                set.Add( f.Key );
            }
            return set;
        }
        public static Boolean IsValid ( this DigestField list, List<DigestFieldValidator> validators ) {
            return true;
        }
        public static void Validate ( this  List<DigestFieldValidator> validators, List<DigestField> list ) {
            List<String> errors = new List<string>( );

            foreach ( DigestFieldValidator validator in validators ) {
                int c = list.GetFieldsByKey( validator.Key ).Count;
                if ( !validator.CountValidator( c ) ) {
                    errors.Add( String.Format( "wrong [{0}] fields count [{1}]", validator.Key, c ) );
                }
            }

            if ( errors.Count > 0 ) {
                throw new ValidationException( "there are errors", errors );
            }
        }
    }

}
